/*
 * Class for population of 3D objects
 * especially for statistical analyses
 */
package ij3d.geom;

import java.util.Enumeration;
import java.util.Vector;

/**
 *
 * @author thomas
 */
public class Objects3DPopulation {

    Vector objects;

    /**
     * Conctructor
     */
    public Objects3DPopulation() {
        objects = new Vector();
    }

    /**
     * Add an object to the population
     * @param obj the 3D object to add
     */
    public void addObject(Object3D obj) {
        objects.add(obj);
    }

    /**
     * All distances 2x2 from center to center
     * @return an array[][] of distances
     */
    public double[][] distancesCentre() {
        int s = objects.size();
        double res[][] = new double[s][s];
        Object3D obj1, obj2;
        double dist;

        for (int i = 0; i < s; i++) {
            obj1 = (Object3D) objects.elementAt(i);
            res[i][i] = 0;
            for (int j = i + 1; j < s; j++) {
                obj2 = (Object3D) objects.elementAt(j);
                dist = obj1.distCenter(obj2);
                res[i][j] = dist;
                res[j][i] = dist;
            }
        }
        return res;
    }

    /**
     * All distances 2x2 from border to border
     * @return an array[][] of distances
     */
    public double[][] distancesBorder() {
        int s = objects.size();
        double res[][] = new double[s][s];
        Object3D obj1, obj2;
        double dist;

        for (int i = 0; i < s; i++) {
            obj1 = (Object3D) objects.elementAt(i);
            res[i][i] = 0;
            for (int j = i + 1; j < s; j++) {
                obj2 = (Object3D) objects.elementAt(j);
                dist = obj1.distBorder(obj2);
                res[i][j] = dist;
                res[j][i] = dist;
            }
        }
        return res;
    }

    /**
     * Histogram of all distances center to center
     * @param step to create bins from min to max
     * @return double[][] with 0 vlaues and 1 counts
     */
    public double[][] histogramDistancesCenter(double step) {
        int s = objects.size();
        double[][] dists = this.distancesCentre();

        return histogramDistances(dists, step);
    }

    /**
     * Histogram of all distances border to border
     * @param step to create bins from min to max
     * @return double[][] with 0 vlaues and 1 counts
     */
    public double[][] histogramDistancesBorder(double step) {
        int s = objects.size();
        double[][] dists = this.distancesBorder();

        return histogramDistances(dists, step);
    }

    // function to compute histogram of distances
    private double[][] histogramDistances(double distances[][], double step) {
        int s = objects.size();
        double dmin = distances[0][1];
        double dmax = dmin;
        double d;

        for (int i = 0; i < s; i++) {
            for (int j = i + 1; j < s; j++) {
                d = distances[i][j];
                if (d > dmax) {
                    dmax = d;
                }
                if (d < dmin) {
                    dmin = d;
                }
            }
        }

        int nbins = (int) Math.ceil((dmax - dmin) / step);
        if(nbins<1) nbins=1;
        int idx;
        double[][] res = new double[2][nbins];
        for (int i = 0; i < nbins; i++) {
            res[0][i] = dmin + i * step;
            res[1][i] = 0;
        }

        for (int i = 0; i < s; i++) {
            for (int j = i + 1; j < s; j++) {
                d = distances[i][j];
                idx = (int) Math.floor((d - dmin) / step);
                res[1][idx]++;
            }
        }
        return res;
    }

    /**
     *
     * @param x
     * @param y
     * @param z
     * @return
     */
    public Object3D closestCenter(double x, double y, double z) {
        Object3D res = null;
        double dmin = Double.MAX_VALUE;
        Object3D tmp;
        double d;

        for (Enumeration e = objects.elements(); e.hasMoreElements();) {
            tmp = (Object3D) e.nextElement();
            d = tmp.distPixelCenter(x, y, z);
            if (d < dmin) {
                dmin = d;
                res = tmp;
            }
        }

        return res;
    }

    /**
     *
     * @param x
     * @param y
     * @param z
     * @return
     */
    public Object3D closestBorder(double x, double y, double z) {
        Object3D res = null;
        double dmin = Double.MAX_VALUE;
        Object3D tmp;
        double d;

        for (Enumeration e = objects.elements(); e.hasMoreElements();) {
            tmp = (Object3D) e.nextElement();
            d = tmp.distPixelBorder(x, y, z);
            if (d < dmin) {
                dmin = d;
                res = tmp;
            }
        }

        return res;
    }

    int[] k_Means(int k) {
        int s = objects.size();
        int[] res = new int[s];
        for (int i = 0; i < s; i++) {
            res[i] = 0;
        }
        Point3D[] ck = new Point3D[k];
        double cx[] = new double[k];
        double cy[] = new double[k];
        double cz[] = new double[k];
        int[] nb = new int[k];


        //initialisation
        double xmin, xmax, ymin, ymax, zmin, zmax;
        xmin = ymin = zmin = Double.MAX_VALUE;
        xmax = ymax = zmax = Double.MIN_VALUE;

        Object3D tmp;
        double d, dmin;

        for (Enumeration e = objects.elements(); e.hasMoreElements();) {
            tmp = (Object3D) e.nextElement();
            if (tmp.getCenterX() < xmin) {
                xmin = tmp.getCenterX();
            }
            if (tmp.getCenterY() < ymin) {
                ymin = tmp.getCenterY();
            }
            if (tmp.getCenterZ() < zmin) {
                zmin = tmp.getCenterZ();
            }
            if (tmp.getCenterX() > xmax) {
                xmax = tmp.getCenterX();
            }
            if (tmp.getCenterY() > ymax) {
                ymax = tmp.getCenterY();
            }
            if (tmp.getCenterZ() > zmax) {
                zmax = tmp.getCenterZ();
            }
        }
        double dx = (xmax - xmin) / (k - 1.0);
        double dy = (ymax - ymin) / (k - 1.0);
        double dz = (zmax - zmin) / (k - 1.0);

        for (int i = 0; i < k; i++) {
            ck[i] = new Point3D(xmin + dx * i, ymin + i * dy, zmin + i * dz);
        }

        // loop
        boolean loop = true;
        int idx = 0;

        while (loop) {
            loop = false;
            for (int i = 0; i < s; i++) {
                tmp = (Object3D) objects.elementAt(i);
                idx = 0;
                // for each object find the closest center
                dmin = tmp.distPixelCenter(ck[0].getX(), ck[0].getY(), ck[0].getZ());
                for (int j = 1; j < k; j++) {
                    d = tmp.distPixelCenter(ck[j].getX(), ck[j].getY(), ck[j].getZ());
                    if (d < dmin) {
                        dmin = d;
                        idx = j;
                    }
                }
                // any change in class
                if (idx != res[i]) {
                    loop = true;
                    res[i] = idx;
                }
            }
            // compute new centers
            for (int j = 0; j < k; j++) {
                cx[k] = 0;
                cy[k] = 0;
                cz[k] = 0;
                nb[k] = 0;
            }
            for (int i = 0; i < s; i++) {
                tmp = (Object3D) objects.elementAt(i);
                idx = res[i];
                cx[idx] += tmp.getCenterX();
                cy[idx] += tmp.getCenterY();
                cz[idx] += tmp.getCenterZ();
                nb[idx]++;
            }
            for (int j = 0; j < k; j++) {
                ck[j] = new Point3D(cx[j] / (double) nb[j], cy[j] / (double) nb[j], cz[j] / (double) nb[j]);
            }
        }

        return res;

    }
}
